/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tv.settings.connectivity;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.android.tv.settings.dialog.SettingsLayoutActivity;
import com.android.tv.settings.dialog.Layout;
import com.android.tv.settings.dialog.Layout.Header;
import com.android.tv.settings.dialog.Layout.Action;
import com.android.tv.settings.dialog.Layout.Status;
import com.android.tv.settings.dialog.Layout.Static;
import com.android.tv.settings.dialog.Layout.StringGetter;
import com.android.tv.settings.dialog.Layout.LayoutGetter;
import com.android.tv.settings.R;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * Activity to manage network settings.
 */
public class NetworkActivity extends SettingsLayoutActivity implements
        ConnectivityListener.Listener {

    private static final boolean DEBUG = false;
    private static final String TAG = "NetworkActivity";
    private static final int REQUEST_CODE_ADVANCED_OPTIONS = 1;
    private static final int WIFI_REFRESH_INTERVAL_CAP_MILLIS = 15 * 1000;

    private ConnectivityListener mConnectivityListener;
    private Resources mRes;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mRes = getResources();
        mConnectivityListener = new ConnectivityListener(this, this);
        mConnectivityListener.start();
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        mConnectivityListener.stop();
    }

    @Override
    public void onConnectivityChange(Intent intent) {
        mEthernetConnectedDescription.refreshView();
        mWifiConnectedDescription.refreshView();
    }

    @Override
    public void onResume() {
        if (mWifiShortListLayout != null) {
            mWifiShortListLayout.onWifiListInvalidated();
        }
        if (mWifiAllListLayout != null) {
            mWifiAllListLayout.onWifiListInvalidated();
        }
        super.onResume();
    }

    StringGetter mEthernetConnectedDescription = new StringGetter() {
        private boolean lastIsEthernetConnected;
        @Override
        public String get() {
            lastIsEthernetConnected =
                    mConnectivityListener.getConnectivityStatus().isEthernetConnected();
            int resId = lastIsEthernetConnected ? R.string.connected : R.string.not_connected;
            return mRes.getString(resId);
        }
        @Override
        public void refreshView() {
            if (mConnectivityListener.getConnectivityStatus().isEthernetConnected() !=
                    lastIsEthernetConnected) {
                super.refreshView();
            }
        }
    };

    StringGetter mWifiConnectedDescription = new StringGetter() {
        private boolean lastIsWifiConnected;
        @Override
        public String get() {
            lastIsWifiConnected = mConnectivityListener.getConnectivityStatus().isWifiConnected();
            int resId = lastIsWifiConnected ? R.string.connected : R.string.not_connected;
            return mRes.getString(resId);
        }
        @Override
        public void refreshView() {
            if (mConnectivityListener.getConnectivityStatus().isWifiConnected() !=
                    lastIsWifiConnected) {
                super.refreshView();
            }
        }
    };

    StringGetter mEthernetIPAddress = new StringGetter() {
        public String get() {
            ConnectivityListener.ConnectivityStatus status =
                    mConnectivityListener.getConnectivityStatus();
            if (status.isEthernetConnected()) {
                return mConnectivityListener.getEthernetIpAddress();
            } else {
                return "";
            }
        }
    };

    StringGetter mEthernetMacAddress = new StringGetter() {
        public String get() {
            return mConnectivityListener.getEthernetMacAddress();
        }
    };

    LayoutGetter mEthernetAdvancedLayout = new LayoutGetter() {
        public Layout get() {
            Layout layout = new Layout();
            // Do not check Ethernet's availability here
            // because it might not be active due to the invalid configuration.
            IpConfiguration ipConfiguration = mConnectivityListener.getIpConfiguration();
            if (ipConfiguration != null) {
                int proxySettingsResourceId =
                    (ipConfiguration.getProxySettings() == ProxySettings.STATIC) ?
                        R.string.wifi_action_proxy_manual :
                        R.string.wifi_action_proxy_none;
                int ipSettingsResourceId =
                    (ipConfiguration.getIpAssignment() == IpAssignment.STATIC) ?
                        R.string.wifi_action_static :
                        R.string.wifi_action_dhcp;
                layout
                    .add(new Action.Builder(mRes, ACTION_ETHERNET_PROXY_SETTINGS)
                            .title(R.string.title_wifi_proxy_settings)
                            .description(proxySettingsResourceId).build())
                    .add(new Action.Builder(mRes, ACTION_ETHERNET_IP_SETTINGS)
                            .title(R.string.title_wifi_ip_settings)
                            .description(ipSettingsResourceId).build());
            } else {
                layout
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_internet_connection)
                            .description(R.string.not_connected).build());
            }
            return layout;
        }
    };

    LayoutGetter mEthernetLayout = new LayoutGetter() {
        public Layout get() {
            boolean ethernetConnected =
                    mConnectivityListener.getConnectivityStatus().isEthernetConnected();
            if (ethernetConnected) {
                return new Layout()
                    .add(new Status.Builder(mRes).title(R.string.title_internet_connection)
                            .description(R.string.connected).build())
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_ip_address)
                            .description(mEthernetIPAddress)
                            .build())
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_mac_address)
                            .description(mEthernetMacAddress)
                            .build())
                    .add(new Header.Builder(mRes)
                            .title(R.string.wifi_action_advanced_options_title).build()
                        .add(mEthernetAdvancedLayout)
                     );

            } else {
                return new Layout()
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_internet_connection)
                            .description(R.string.not_connected)
                            .build())
                    .add(new Header.Builder(mRes)
                            .title(R.string.wifi_action_advanced_options_title).build()
                        .add(mEthernetAdvancedLayout)
                    );
            }
        }
    };

    private static final int NUMBER_SIGNAL_LEVELS = 4;
    private static final int ACTION_WIFI_FORGET_NETWORK = 1;
    private static final int ACTION_WIFI_PROXY_SETTINGS = 4;
    private static final int ACTION_WIFI_IP_SETTINGS = 5;
    private static final int ACTION_ETHERNET_PROXY_SETTINGS = 6;
    private static final int ACTION_ETHERNET_IP_SETTINGS = 7;

    private final Context mContext = this;

    private int getCurrentNetworkIconResourceId(
            ScanResult scanResult, int signalLevel) {
        int resourceId = 0;
        if (scanResult != null) {
            WifiSecurity security = WifiSecurity.getSecurity(scanResult);
            if (security.isOpen()) {
                switch (signalLevel) {
                    case 0:
                        resourceId = R.drawable.ic_settings_wifi_active_1;
                        break;
                    case 1:
                        resourceId = R.drawable.ic_settings_wifi_active_2;
                        break;
                    case 2:
                        resourceId = R.drawable.ic_settings_wifi_active_3;
                        break;
                    case 3:
                        resourceId = R.drawable.ic_settings_wifi_active_4;
                        break;
                }
            } else {
                switch (signalLevel) {
                    case 0:
                        resourceId = R.drawable.ic_settings_wifi_secure_active_1;
                        break;
                    case 1:
                        resourceId = R.drawable.ic_settings_wifi_secure_active_2;
                        break;
                    case 2:
                        resourceId = R.drawable.ic_settings_wifi_secure_active_3;
                        break;
                    case 3:
                        resourceId = R.drawable.ic_settings_wifi_secure_active_4;
                        break;
                }
            }
        }
        return resourceId;
    }

    private String getSignalStrength() {
        String[] signalLevels = mRes.getStringArray(R.array.wifi_signal_strength);
        int strength = mConnectivityListener.getWifiSignalStrength(signalLevels.length);
        return signalLevels[strength];
    }

    LayoutGetter mWifiInfoLayout = new LayoutGetter() {
        public Layout get() {
            Layout layout = new Layout();
            ConnectivityListener.ConnectivityStatus status =
                    mConnectivityListener.getConnectivityStatus();
            boolean isConnected = status.isWifiConnected();
            if (isConnected) {
                layout
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_internet_connection)
                            .description(R.string.connected).build())
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_ip_address)
                            .description(mConnectivityListener.getWifiIpAddress()).build())
                    .add(new Status.Builder(mRes)
                             .title(R.string.title_mac_address)
                            .description(mConnectivityListener.getWifiMacAddress()).build())
                    .add(new Status.Builder(mRes)
                             .title(R.string.title_signal_strength)
                            .description(getSignalStrength()).build());
            } else {
                layout
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_internet_connection)
                            .description(R.string.not_connected).build());
            }
            return layout;
        }
    };

    LayoutGetter mWifiAdvancedLayout = new LayoutGetter() {
        public Layout get() {
            Layout layout = new Layout();
            WifiConfiguration wifiConfiguration = mConnectivityListener.getWifiConfiguration();
            if (wifiConfiguration != null) {
                int proxySettingsResourceId =
                    (wifiConfiguration.getProxySettings() == ProxySettings.NONE) ?
                        R.string.wifi_action_proxy_none :
                        R.string.wifi_action_proxy_manual;
                int ipSettingsResourceId =
                   (wifiConfiguration.getIpAssignment() == IpAssignment.STATIC) ?
                        R.string.wifi_action_static :
                        R.string.wifi_action_dhcp;
                layout
                    .add(new Action.Builder(mRes, ACTION_WIFI_PROXY_SETTINGS)
                            .title(R.string.title_wifi_proxy_settings)
                            .description(proxySettingsResourceId).build())
                    .add(new Action.Builder(mRes, ACTION_WIFI_IP_SETTINGS)
                            .title(R.string.title_wifi_ip_settings)
                            .description(ipSettingsResourceId).build());
            } else {
                layout
                    .add(new Status.Builder(mRes)
                            .title(R.string.title_internet_connection)
                            .description(R.string.not_connected).build());
            }
            return layout;
        }
    };

    private void addWifiConnectedHeader(Layout layout, String SSID) {
        layout
            .add(new Header.Builder(mRes)
                    .title(SSID)
                    .description(R.string.connected).build()
                .add(new Header.Builder(mRes)
                        .title(R.string.wifi_action_status_info).build()
                    .add(mWifiInfoLayout)
                )
                .add(new Header.Builder(mRes)
                        .title(R.string.wifi_action_advanced_options_title).build()
                    .add(mWifiAdvancedLayout)
                )
                .add(new Header.Builder(mRes)
                        .title(R.string.wifi_forget_network).build()
                    .add(new Action.Builder(mRes, ACTION_WIFI_FORGET_NETWORK)
                            .title(R.string.title_ok).build())
                    .add(new Action.Builder(mRes, Action.ACTION_BACK)
                            .title(R.string.title_cancel).build())
                 )
            );
    }

    private class WifiListLayout extends LayoutGetter implements
            ConnectivityListener.WifiNetworkListener {
        private final boolean mTop3EntriesOnly;
        private String mSelectedTitle;
        private long mLastWifiRefresh = 0;

        private final Runnable mRefreshViewRunnable = new Runnable() {
            @Override
            public void run() {
                Layout.Node selected = getSelectedNode();
                if (selected != null) {
                    mSelectedTitle = selected.getTitle();
                }
                refreshView();
            }
        };

        WifiListLayout(boolean top3EntriesOnly) {
            mTop3EntriesOnly = top3EntriesOnly;
        }

        @Override
        public Layout get() {
            mConnectivityListener.startScanningWifi(this);
            mLastWifiRefresh = SystemClock.elapsedRealtime();
            mHandler.removeCallbacks(mRefreshViewRunnable);
            return initAvailableWifiNetworks(mTop3EntriesOnly, mSelectedTitle).
                    setSelectedByTitle(mSelectedTitle);
        }

        @Override
        public void onMovedOffScreen() {
            mHandler.removeCallbacks(mRefreshViewRunnable);
            mConnectivityListener.stopScanningWifi(this);
        }

        @Override
        public void onWifiListChanged() {
            long now = SystemClock.elapsedRealtime();
            long millisToNextRefreshView =
                    WIFI_REFRESH_INTERVAL_CAP_MILLIS - now + mLastWifiRefresh;
            mHandler.removeCallbacks(mRefreshViewRunnable);
            mHandler.postDelayed(mRefreshViewRunnable, millisToNextRefreshView);
        }

        /**
         * Wifi network configuration has changed and an immediate refresh of the list of Wifi
         * networks is required.
         */
        public void onWifiListInvalidated() {
            mLastWifiRefresh = 0;
            onWifiListChanged();
        }

        /**
         * Create a list of available Wifi networks sorted by connection status (a connected Wifi
         * network is shown at the first position on the list) and signal strength, with the
         * provisio that the wireless network with SSID "mustHave" should be included in the list
         * even if it would be otherwise excluded.
         *
         * @param top3EntriesOnly Show only 3 entries in the list.
         * @param mustHave        Include this wifi network in the list even if it would otherwise
         *                        be excluded by virtue of inadequate signal strength.
         */
        private Layout initAvailableWifiNetworks(boolean top3EntriesOnly, String mustHave) {
            List<ScanResult> networks = mConnectivityListener.getAvailableNetworks();
            Layout layout = new Layout();
            if (networks.size() > 0) {
                int maxItems = top3EntriesOnly ? 3 : Integer.MAX_VALUE;
                // "networks" is already sorted by the signal strength and connection status.
                // Generate a new list with size less than "maxItems" that ensures "mustHave" is
                // included.
                boolean haveMustHave = false;
                List<ScanResult> displayList = new ArrayList<ScanResult>();
                for (ScanResult scanResult : networks) {
                    if (!haveMustHave && TextUtils.equals(scanResult.SSID, mustHave)) {
                        haveMustHave = true;
                        if (displayList.size() == maxItems) {
                            displayList.remove(maxItems-1);
                        }
                        displayList.add(scanResult);
                    } else if (displayList.size() < maxItems) {
                        displayList.add(scanResult);
                    }
                    if (haveMustHave && displayList.size() == maxItems) {
                        break;
                    }
                }

                // If a network is connected, it will be the first on the list.
                boolean isConnected =
                    mConnectivityListener.getConnectivityStatus().isWifiConnected();
                for (ScanResult network : displayList) {
                    if (network != null) {
                        WifiSecurity security = WifiSecurity.getSecurity(network);

                        String networkDescription =
                            security.isOpen() ? "" : security.getName(mContext);
                        Intent intent =
                            WifiConnectionActivity.createIntent(mContext, network, security);
                        int signalLevel = WifiManager.calculateSignalLevel(
                                network.level, NUMBER_SIGNAL_LEVELS);
                        //TODO implement signal dependent list icon.
                        /*
                        int imageResourceId = getNetworkIconResourceId(network, signalLevel);
                        if (WifiConfigHelper.areSameNetwork(mWifiManager, network,
                                currentConnection)) {
                            networkDescription = getString(R.string.connected);
                            signalLevel = WifiManager.calculateSignalLevel(
                                    currentConnection.getRssi(), NUMBER_SIGNAL_LEVELS);
                            imageResourceId = getCurrentNetworkIconResourceId(network, signalLevel);
                        } */

                        if (isConnected) {
                            addWifiConnectedHeader(layout, network.SSID);
                        } else {
                            layout.add(new Action.Builder(mRes, intent)
                                    .title(network.SSID)
                                    .description(networkDescription).build());
                        }
                    }
                    isConnected = false;
                }
            } else {
                layout.add(new Action.Builder(mRes, 0)
                       .title(R.string.title_wifi_no_networks_available).build());
            }
            return layout;
        }
    };

    private final WifiListLayout mWifiShortListLayout = new WifiListLayout(true);

    private final WifiListLayout mWifiAllListLayout = new WifiListLayout(false);

    @Override
    public Layout createLayout() {
        return
            new Layout()
                .breadcrumb(getString(R.string.header_category_device))
                .add(new Header.Builder(mRes)
                        .icon(R.drawable.ic_settings_wifi_4)
                        .title(R.string.connectivity_network)
                        .description(mWifiConnectedDescription)
                        .build()
                    .add(new Header.Builder(mRes).title(R.string.connectivity_wifi)
                            .contentIconRes(R.drawable.ic_settings_wifi_4)
                            .description(mWifiConnectedDescription).build()
                        .add(new Static.Builder(mRes)
                                .title(R.string.wifi_setting_available_networks)
                                .build())
                        .add(mWifiShortListLayout)
                        .add(new Header.Builder(mRes)
                                .title(R.string.wifi_setting_see_all)
                                .build()
                            .add(mWifiAllListLayout)
                        )
                        .add(new Static.Builder(mRes)
                                .title(R.string.wifi_setting_header_other_options)
                                .build())
                        .add(new Action.Builder(mRes,
                                 new Intent(this, WpsConnectionActivity.class))
                                .title(R.string.wifi_setting_other_options_wps)
                                .build())
                        .add(new Action.Builder(mRes,
                                new Intent(this, AddWifiNetworkActivity.class))
                                .title(R.string.wifi_setting_other_options_add_network)
                                .build())
                    )
                    .add(new Header.Builder(mRes)
                            .title(R.string.connectivity_ethernet)
                            .contentIconRes(R.drawable.ic_settings_ethernet)
                            .description(mEthernetConnectedDescription)
                            .build()
                        .add(mEthernetLayout)
                     )
                 );
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE_ADVANCED_OPTIONS && resultCode == RESULT_OK) {
            //TODO make sure view reflects model deltas
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    public void onActionFocused(Layout.LayoutRow item) {
        int resId = item.getContentIconRes();
        if (resId != 0) {
            setIcon(resId);
        }
    }

    @Override
    public void onActionClicked(Action action) {
        switch (action.getId()) {
            case Action.ACTION_INTENT:
                startActivityForResult(action.getIntent(), REQUEST_CODE_ADVANCED_OPTIONS);
                break;
            case ACTION_WIFI_FORGET_NETWORK:
                mConnectivityListener.forgetWifiNetwork();
                goBackToTitle(mRes.getString(R.string.connectivity_wifi));
                mWifiShortListLayout.onWifiListInvalidated();
                mWifiAllListLayout.onWifiListInvalidated();
                break;
            case ACTION_WIFI_PROXY_SETTINGS: {
                int networkId = mConnectivityListener.getWifiNetworkId();
                if (networkId != -1) {
                    startActivityForResult(EditProxySettingsActivity.createIntent(this, networkId),
                            REQUEST_CODE_ADVANCED_OPTIONS);
                }
                break;
            }
            case ACTION_WIFI_IP_SETTINGS: {
                int networkId = mConnectivityListener.getWifiNetworkId();
                if (networkId != -1) {
                    startActivityForResult(EditIpSettingsActivity.createIntent(this, networkId),
                            REQUEST_CODE_ADVANCED_OPTIONS);
                }
                break;
            }
            case ACTION_ETHERNET_PROXY_SETTINGS: {
                int networkId = WifiConfiguration.INVALID_NETWORK_ID;
                startActivityForResult(EditProxySettingsActivity.createIntent(this, networkId),
                        REQUEST_CODE_ADVANCED_OPTIONS);
                break;
            }
            case ACTION_ETHERNET_IP_SETTINGS: {
                int networkId = WifiConfiguration.INVALID_NETWORK_ID;
                startActivityForResult(EditIpSettingsActivity.createIntent(this, networkId),
                        REQUEST_CODE_ADVANCED_OPTIONS);
                break;
            }
        }
    }
}
